package com.echo.code.thread.lock.impl;

import com.echo.code.thread.lock.Lock;

import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Logger;

import static java.lang.System.currentTimeMillis;

/**
 * SimpleLock Created by baiyap on 2018/9/24.
 */
public class SimpleLock implements Lock {

    private final static Logger LOG = Logger.getLogger(SimpleLock.class.getName());
    private final Set<Thread> blockedSet = new HashSet<>();
    private boolean locked = false;
    private Thread currentThread;

    @Override
    public void lock() throws InterruptedException {
        Thread thread = Thread.currentThread();
        synchronized (this) {
            if (Objects.equals(thread, currentThread)) {
                return;
            }
            while (locked) {
                blockedSet.add(thread);
                LOG.info(getCurrentThreadName() + " can not get the lock and suspend...");
                try {
                    this.wait();
                } catch (InterruptedException ite) {
                    blockedSet.remove(thread);
                    throw ite;
                }
            }
            locked = true;
            blockedSet.remove(thread);
            currentThread = thread;
            LOG.info(getCurrentThreadName() + " obtain the lock.");
        }
    }

    public static void main(String[] args) {
        Lock lock = new SimpleLock();
        Thread task = new Thread(() -> {
            System.out.println("before lock 1");
            try {
                lock.lock();
            } catch (InterruptedException e) {

            }
            System.out.println("after lock 1");
            System.out.println("before lock 2");
            try {
                lock.lock();
            } catch (InterruptedException e) {

            }
            System.out.println("after lock 2");
        });
        task.start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {

        }
    }

    @Override
    public void lock(long millis) throws InterruptedException, TimeoutException {
        synchronized (this) {
            if (millis <= 0l) {
                this.lock();
            } else {
                Thread thread = Thread.currentThread();
                long remainMillis = millis;
                long endMillis = currentTimeMillis() + remainMillis;
                while (locked) {
                    if (remainMillis <= 0) {
                        blockedSet.remove(thread);
                        throw new TimeoutException();
                    }
                    blockedSet.add(thread);
                    LOG.info(getCurrentThreadName() + " can not get the lock and suspend...");
                    try {
                        this.wait(remainMillis);
                    } catch (InterruptedException ite) {
                        blockedSet.remove(thread);
                        throw ite;
                    }
                    remainMillis = endMillis - currentTimeMillis();
                }
                blockedSet.remove(thread);
                locked = true;
                currentThread = thread;
                LOG.info(getCurrentThreadName() + " obtain the lock.");
            }
        }
    }

    @Override
    public void unlock() {
        synchronized (this) {
            if (Objects.equals(currentThread, Thread.currentThread())) {
                this.locked = false;
                currentThread = null;
                this.notifyAll();
                LOG.info(getCurrentThreadName() + " release the lock.");
            }
        }
    }
}
